package com.joshua.spring.integration.exception;

import com.joshua.spring.entity.em.ResponseEnum;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import javax.security.sasl.AuthenticationException;
import java.io.IOException;

import static org.springframework.http.HttpHeaders.*;

/**
 * com.joshua.spring.business.integration.exception -- IntegrationWebResponseExceptionTranslator
 * description: 资源服务器异常自定义捕获
 *
 * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
 * @date 2020/1/8 11:07
 */
@Component
public class IntegrationWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {
    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    private static final String INVALID_REFRESH = "Invalid refresh token";

    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(e);
        Exception causeChainException = (OAuth2Exception) this.throwableAnalyzer
                .getFirstThrowableOfType(OAuth2Exception.class, causeChain);
        if (causeChainException != null) {
            //异常链中包含过期的refresh_token
            if (causeChainException instanceof InvalidGrantException) {
                return this.handleOAuth2Exception(new IntegrationDetailException(
                        ResponseEnum.TOKEN_EXPIRE,
                        e.getMessage(), e));
            }
        }
        //本地身份验证相关
        causeChainException = (IntegrationAuthenticationServiceException) this.throwableAnalyzer
                .getFirstThrowableOfType(IntegrationAuthenticationServiceException.class, causeChain);
        if (causeChainException != null) {
            IntegrationAuthenticationServiceException exception = (IntegrationAuthenticationServiceException) causeChainException;
            return this.handleOAuth2Exception(new IntegrationDetailException(
                    exception.getExceptionEnum(),
                    e.getMessage(), e));
        }
        //身份验证相关异常
        causeChainException = (AuthenticationException) this.throwableAnalyzer
                .getFirstThrowableOfType(AuthenticationException.class, causeChain);
        if (causeChainException != null) {
            return this.handleOAuth2Exception(new IntegrationDetailException(
                    ResponseEnum.UNAUTHORIZED,
                    e.getMessage(), e));
        }
        //异常链中包含拒绝访问异常
        causeChainException = (AccessDeniedException) this.throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (causeChainException != null) {
            return this.handleOAuth2Exception(new IntegrationDetailException(
                    ResponseEnum.FORBIDDEN,
                    e.getMessage(), e));
        }
        //异常链中包含Http方法请求异常
        causeChainException = (HttpRequestMethodNotSupportedException) this.throwableAnalyzer
                .getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);
        if (causeChainException != null) {
            return this.handleOAuth2Exception(new IntegrationDetailException(
                    ResponseEnum.METHOD_NOT_ALLOW,
                    e.getMessage(), e));
        }
        return this.handleOAuth2Exception(new OAuth2Exception(e.getMessage(), e));
    }

    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {
        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set(CACHE_CONTROL, "no-store");
        headers.set(PRAGMA, "no-cache");
        if (status == ResponseEnum.UNAUTHORIZED.getHttpStatus().value() || e instanceof InsufficientScopeException) {
            headers.set(WWW_AUTHENTICATE, String.format("%s %s", "Bearer", e.getSummary()));
        }
        IntegrationException exception;
        ResponseEntity<OAuth2Exception> response;
        if (e instanceof IntegrationDetailException) {
            IntegrationDetailException integrationDetailException = (IntegrationDetailException) e;
            exception = new IntegrationException(e.getMessage(), e, integrationDetailException.getCode());
        } else {
            exception = new IntegrationException(e.getMessage(), e);
        }
        response = new ResponseEntity<>(exception, headers, HttpStatus.valueOf(status));
        return response;
    }
}
